The Developer Fastlane

« 365 days to become a developer » challenge

PHP: Login system

November 16, 2020

Instructions & result

Goal: Prevent anyone who is not an admin to access the dashboard (created in the previous exercise). This exercise will take part of sessions.

  • If the user arrives on the dashboard.php page without being logged in, he is redirected to a login form.
  • On the login page, he must enter a login and a password.
  • If these two values match what is expected, he will be redirected to dashboard.php.
  • If they don't match: error message and no redirection.

Result
Click on one of the images above to access the exercise's website

Code

Steps

  1. Create a system to block the user (check if user is an admin or not: session_start() + $_SESSION)
    • How to check url ONLY the first time the user access the page ?
  2. Redirect the user: header() function
    • If is admin (isset $_SESSION): redirect to dashboard.php
    • If is not admin: redirect to login.php
  3. Create login form (login.php + "POST" method)
  4. Define an id and a password, then hash it.
  5. Check if infos match: reuse the step 2 function for checking

header.php

Code
<?php

require_once 'functions_login.php';
require_once 'functions.php';

?>

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="Edouard Proust">
    <meta name="generator" content="The Developer Fastlane">
    <title><?php title_dyn($title) ?></title>

    <link rel="canonical" href="https://getbootstrap.com/docs/4.5/examples/starter-template/">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
    <link rel="stylesheet" href="assets/style.css">
    
<!-- Favicons -->
<link rel="apple-touch-icon" href="/docs/4.5/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
<link rel="icon" href="/docs/4.5/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="/docs/4.5/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="manifest" href="/docs/4.5/assets/img/favicons/manifest.json">
<link rel="mask-icon" href="/docs/4.5/assets/img/favicons/safari-pinned-tab.svg" color="#563d7c">
<link rel="icon" href="/docs/4.5/assets/img/favicons/favicon.ico">
<meta name="msapplication-config" content="/docs/4.5/assets/img/favicons/browserconfig.xml">
<meta name="theme-color" content="#563d7c">


    <style>
      .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }

      @media (min-width: 768px) {
        .bd-placeholder-img-lg {
          font-size: 3.5rem;
        }
      }
    </style>

  </head>
  <body>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
  <div class="navbar-brand" href="<?= dirbase() ?>">PHP Project</div>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>

  <div class="collapse navbar-collapse" id="navbarsExampleDefault">
    <ul class="navbar-nav mr-auto">
      <?= nav_menu('nav-link') ?>
    </ul>
      <?php if (is_connected()): ?>
        <button onclick="location.href='logout.php'" type="button" class="btn btn-danger mr-2">Log out</button>
      <?php endif; ?> 
      <button onclick="location.href='/'" type="button" class="btn btn-secondary my-2 my-sm-0">Back to lessons list</button>
  </div>
</nav>

<main role="main" class="container">
  <div class="starter-template">
    <h1><?= $title ?></h1>

dashboard.php

Code
<?php

require_once "includes/functions_login.php";
if (!is_connected()) {
  header('Location: login.php');
}
  
$title = "Dashboard";
require_once 'includes/header.php'; 
require_once 'includes/functions_analytics.php'; 
require_once 'includes/functions_counters.php'; // We need to reinclude it (in the footer is too late)
?>
<p class="lead mb-5">This dashboard lists all the statistics on the website since it was created. Click on years and months listed on the left to access the detailed data.</p>

<div class="row">

  <!-- MENU (left) -->

  <div class="col-md-4">
    <div class="list-group mb-4">
      <?php  
        for ($year = (int)date('Y'); $year > (int)date('Y') - 5; $year--): ?>
          <a href="?year=<?= $year ?>" class="list-group-item <?= li_active('year', $year) ?>">
            <b><?= $year ?></b>
          </a><?php 
          if ( (int)$_GET['year'] === $year ): // List months
            if ($_GET['year'] !== date("Y")): $m = 12; else: $m = date('m'); endif; 
              // If clicked is the current year, then display ONLY past months
            for($i=$m;$i>0;$i--): ?>
              <a href="?year=<?= $year ?>&month=<?= $i ?>" class="list-group-item <?= li_active('month', $i) ?>">
                  <?= $breadcrumb[$year][] = date('F',strtotime('01.'.$i.'.2000')); // To list months in letters ?>
              </a><?php 
            endfor;
          endif;
        endfor; ?>
    </div>
  </div>

  <!-- DATA (right) -->

  <div class="col-md-8">
    <div class="card mb-3">

      <!-- Breadcrumb --> 

      <nav aria-label="breadcrumb">
        <ol class="breadcrumb">
          <?php $breadcrumb = breadcrumb_dash(); ?>
            <li class="breadcrumb-item active"><?= $breadcrumb['total'] ?></li>
            <?php if (isset($_GET['year'])): ?>
              <li class="breadcrumb-item active"><?= $breadcrumb['year'] ?></li>
            <?php endif; ?>
            <?php if (isset($_GET['month'])): ?>
              <li class="breadcrumb-item active"><?= $breadcrumb['month'] ?></li>
            <?php endif; ?>
        </ol>
      </nav>

      <!-- Card (year & month)--> 

      <div class="card-body">
        <?php if (!isset($_GET['year'])): ?>
          <div class="row">
            <div class="col-sm mb-3">
              <h3><?= counter_sum('total'); ?></h3>Visited pages
            </div>
            <div class="col-sm">
              <h3><?= counter_sum(date("Y-m-d")); ?></h3>Today
            </div>
          </div>
        <?php elseif (!isset($_GET['month'])) : ?>
          <h3><?= counter_sum('year', $_GET['year']) ?></h3>Visited pages
        <?php endif; ?>
        <?php if (isset($_GET['month'])): ?>
          <h3><?= counter_sum('month', $_GET['year'], $_GET['month']) ?></h3>Visited pages
      </div>

    </div>

    <!-- Table (days) -->

    <div class="card">
      <table class="table">
        <thead class="thead-light">
            <tr>
              <th>Day</th>
              <th>Page views</th>
            </tr>
        </thead>
        <tbody>
          <?php if (counter_sum('month', $_GET['year'], $_GET['month']) > 0 ):
            for ($i=31; $i>0; $i--):
                $day_file = $_GET['year'] . '-' . str_pad($_GET['month'], 2, '0', STR_PAD_LEFT) . '-' . str_pad($i, 2, '0', STR_PAD_LEFT); 
                if (file_exists(counter_page_views_db($day_file))): ?>
                  <tr>
                    <td><?= $i ?></td>
                    <td><?= counter_page_views_int($day_file); ?></td>
                  </tr>
                <?php endif; ?>
            <?php endfor; ?>
          <?php else: ?>
            <tbody>
              <tr>
                <td colspan="2">No data</td>
              </tr>
            </tbody>
          <?php endif; ?>
        </tbody>
      </table>
    </div>

    <?php endif; ?>

  </div>
</div>

<?php require 'includes/footer.php'; ?><?php

require_once 'includes/functions_login.php';
$alert_message = $alert_color = '';
if ($_GET['action'] === 'logout') { 
  // This condition must be before the credentials check (for the case the user logged out, then try to connect again and made a mistake in credentials)
  $alert_message = 'You have been successfully logged out';
  $alert_color = 'success';
}
if (!empty($_POST)) {
  if ($_POST['username'] === 'admin' && password_verify($_POST['password'], '$2y$13$ew/.dV4.LdNxbCBkdxaELe4ozhg395dZ5jZCM/1sEuXUFUkrIlubi') === true ) {
    session_start();
    $_SESSION['connected'] = 1;
    header('Location: dashboard.php');
  } else {
    $alert_message = 'Incorrect username and/or password';
    $alert_color = 'danger';
  }
}

$title = "Sign in";
require_once 'includes/header.php';
?>

  <p class="lead">You must be connected to access this page.</p>

  <div class="row">
    <div class="col-md-4">
      <?php if($error): ?>
        <div class="alert alert-danger"><?= $error ?></div>
      <?php endif; ?>
      <p class="small">Use these credentials to access:<br>
      - Username: <b>admin</b><br>
      - Password: <b>php</b></p>
      <form action="" method="POST">
        <div class="form-group">
          <label>Username</label>
          <input type="text" name="username" class="form-control">
        </div>
        <div class="form-group">
          <label>Password</label>
          <input type="password" name="password" class="form-control">
        </div>
        <button type="submit" class="btn btn-primary">Sign in</button>
      </form>
    </div>
  </div>
<?php require 'includes/footer.php'; ?>

logout.php

Code
<?php

session_start();
unset($_SESSION['connected']);
header('Location: login.php?action=logout');
<?php

function is_connected(): bool 
{
    if( session_status() === PHP_SESSION_NONE ) {
        session_start();
    }
    return !empty($_SESSION['connected']); // Returns TRUE if $_SESSION['connected'] is not empty
}
© 2020 - Edouard Proust | The Developer Fastlane